﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;

namespace lipesin.Main
{
	/// <summary>
	/// Разделителями, используемыми в файле, являются:
	/// @@@ - признак конца фрагмента (конец служебной или информационной частей сообщения);
	/// ### - признак конца блока;
	/// === - признак конца файла.
	/// </summary>
	public class ExportDogsFormat : IDisposable
	{
		public enum Separators
		{
			/// <summary>===</summary>
			EndFile,
			/// <summary>@@@</summary>
			EndSection,
			/// <summary>###</summary>
			EndBlock
		}
		private StreamWriter _writer;
		private int _currentLineNumber = 0;
		public List<string> _currentLineBuffer = new List<string>();
		private string _tag = null;
		private bool _required;
		private int _itemNumber;
		public List<string> Errors { get; set; }

		public ExportDogsFormat(StreamWriter writer)
		{
			_writer = writer;
			Errors = new List<string>();
		}
		public void Dispose()
		{
			WriteLineBuffer();
			_writer.Close();
			_writer.Dispose();
		}
		private void AddError(string error)
		{
			Errors.Add(string.Format("Строка {0} тег {1} элемент {2}: {3}", _currentLineNumber, _tag, _itemNumber, error));
		}
		public void NewLine(string tag, bool required)
		{
			WriteLineBuffer();
			_tag = tag;
			_required = true;
		}
		private void WriteLineBuffer()
		{
			if (_tag == null) return;
			if (_required || _currentLineBuffer.Count > 2 || (_currentLineBuffer.Count == 1 && _currentLineBuffer[0].Length > 0))
			{
				_writer.WriteLine(_tag + ":" + string.Join(",", _currentLineBuffer));
			}
			_tag = null;
			_currentLineBuffer.Clear();
			_itemNumber = 0;
			_currentLineNumber++;
		}
		/// <summary>
		/// Добавляет элемент к текущему тегу
		/// </summary>
		/// <param name="format">
		/// форматная строка вида "T2.12", "N15.2", "-K3"
		/// префикс "-" - необязательный артрибут (в формате пометка |E(0)).
		/// символ - тип атрибута:
		/// D - дата формата dd.mm.yyyy
		/// N15.2 - числовой тип. первое число - максимальное количество знаков в числе, включая целую и дробную часть числа, десятичную точку и знак "-", второе число(если есть) число знаков дробной части.
		/// K5 - кодовый тип. цифры с ведущими нулями.
		/// T0.20 - текст. первая цифра - тип текста, вторая цифра - максимальная длинна.
		///   типы:
		///   0 - (<буква>|<ц>|<другой символ>)[{<символ>}]
		///   1 - ((<буква>|<ц>)[{<буква>|<ц>|<пробел>|-|/}])|<пустое выражение>
		///   2 - (<буква>[{<буква>|<пробел>|-}])|<пустое выражение>
		/// C - валюта. псевдоним N15.2.
		/// </param>
		/// <param name="value">выводимое значение</param>
		/// <returns>сформированный атрибут для дополнительных проверок</returns>
		private string AddItem(string format, object value)
		{
			_itemNumber++;
			if (string.IsNullOrEmpty(format) || format.Length < 1) throw new Exception("слишком короткая форматная строка.");
			bool allowEmpty = false;
			if (format.Substring(0, 1) == "-")
			{
				allowEmpty = true;
				format = format.Substring(1);
			}
			if (format.Length < 1) throw new Exception("Нет типа аттрибута.");
			if (format[0] == 'C') format = "N15.2";
			string[] numbers = new string[0];
			if (format.Length > 1) numbers = format.Substring(1).Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
			int maxLength = -1;
			string result = string.Empty;
			if (!Convert.IsDBNull(value) && value != null)
			{
				switch (format[0])
				{
					case 'D':
						result = string.Format("{0:dd.MM.yyyy}", value);
						maxLength = 10;
						break;
					case 'N':
						if (numbers.Length < 1) throw new Exception("Нет номера в формате N.");
						maxLength = Convert.ToInt32(numbers[0]);
						string nformat = "{0:#0}";
						if (numbers.Length > 1) nformat = "{0:#0." + string.Empty.PadRight(Convert.ToInt32(numbers[1]), '0') + "}";
						result = string.Format(new System.Globalization.CultureInfo("en-US", false).NumberFormat, nformat, value);
						break;
					case 'K':
						if (numbers.Length < 1) throw new Exception("Нет номера в формате K.");
						maxLength = Convert.ToInt32(numbers[0]);
						string kformat = "{0:" + string.Empty.PadRight(maxLength, '0') + "}";
						result = string.Format(kformat, value);
						break;
					case 'T':
						if (numbers.Length < 2) throw new Exception("Нет номера и максимальной длинны в формате T.");
						allowEmpty = true;
						maxLength = Convert.ToInt32(numbers[1]);
						string tformat;
						switch (Convert.ToInt32(numbers[0]))
						{
							case 0:
								tformat = @"^[^\s].*$";//первый сивол не пробел, а дальше что угодно
								break;
							case 1:
								tformat = @"^([\dа-яА-Яa-zA-Z]([\dа-яА-Яa-zA-Z\s-/])*)*$";//<текст1>::=((<буква>|<ц>)[{<буква>|<ц>|<пробел>|-|/}])|<пустое выражение>
								break;
							case 2:
								tformat = @"^([а-яА-Яa-zA-Z]([а-яА-Яa-zA-Z\s-])*)*$";//<текст2>::=(<буква>[{<буква>|<пробел>|-}])|<пустое выражение>
								break;
							default: throw new Exception("Неизвестный тип текста.");
						}
						result = value.ToString();
						if (result.IndexOf('\r') >= 0) result = result.Replace("\r", string.Empty);
						if (result.IndexOf('\n') >= 0) result = result.Replace("\n", string.Empty);
						if (!Regex.IsMatch(result, tformat)) AddError("Текст не соответствует формату.");
						break;
					default: throw new Exception("Неизвестный тип аттрибута.");
				}
			}
			if (maxLength >= 0 && result.Length > maxLength) AddError("Превышена максимальная длинна.");
			if (!allowEmpty && result.Length == 0) AddError("Незаполненный элемент.");
			_currentLineBuffer.Add(result);

			return result;
		}
		public void WriteSeparator(Separators separator)
		{
			WriteLineBuffer();
			switch (separator)
			{
				case Separators.EndFile: _writer.WriteLine("==="); break;
				case Separators.EndSection: _writer.WriteLine("@@@"); break;
				case Separators.EndBlock: _writer.WriteLine("###"); break;
			}
		}
		public static void CreateTestFile()
		{
			if (!System.IO.Directory.Exists(@"E:\tmp\trash")) System.IO.Directory.CreateDirectory(@"E:\tmp\trash");
			StreamWriter writer = new StreamWriter(@"E:\tmp\trash\DogExport.txt", false);
			ExportDogsFormat e = new ExportDogsFormat(writer);
			object value;
			e.NewLine("T0", true);
			value = "563   ";
			e.AddItem("T0.3", value);

			e.NewLine("T1", true);
			value = "+5";
			e.AddItem("T1.50", value);

			e.NewLine("T2", true);
			value = "";
			e.AddItem("T2.50", value);

			e.NewLine("C", true);
			value = 555.5555555;
			e.AddItem("C", value);

			e.NewLine("N8", true);
			value = 555.5555555;
			e.AddItem("N8", value);

			e.WriteSeparator(Separators.EndBlock);

			e.NewLine("K3", true);
			value = 35;
			e.AddItem("K3", value);

			e.NewLine("-K1", true);
			value = DBNull.Value ;
			e.AddItem("-K1", value);

			e.WriteSeparator(Separators.EndSection);

			e.NewLine("D", true);
			value = DateTime.Now;
			e.AddItem("D", value);

			e.WriteSeparator(Separators.EndFile);

			e.Dispose();

			List<string> errors = e.Errors;
		}
	}
}
